import fs from 'fs';
import path from 'path';
import {throwError} from '../utils.js';
import watchFile from '../watchFile.js';

function runOnce(fn: () => void) {
  if (process.env._NEXT_INTL_COMPILE_MESSAGES === '1') {
    return;
  }
  process.env._NEXT_INTL_COMPILE_MESSAGES = '1';
  fn();
}

export default function createMessagesDeclaration(
  messagesPaths: Array<string>
) {
  // Instead of running _only_ in certain cases, it's
  // safer to _avoid_ running for certain known cases.
  // https://github.com/amannn/next-intl/issues/2006
  const shouldBailOut = [
    'info',
    'start'

    // Note: These commands don't consult the
    // Next.js config, so we can't detect them here.
    // - telemetry
    // - lint
    //
    // What remains are:
    // - dev
    // - build
    // - typegen
  ].some((arg) => process.argv.includes(arg));
  if (shouldBailOut) {
    return;
  }

  // Next.js can call the Next.js config multiple
  // times - ensure we only run once.
  runOnce(() => {
    for (const messagesPath of messagesPaths) {
      const fullPath = path.resolve(messagesPath);

      if (!fs.existsSync(fullPath)) {
        throwError(
          `\`createMessagesDeclaration\` points to a non-existent file: ${fullPath}`
        );
      }
      if (!fullPath.endsWith('.json')) {
        throwError(
          `\`createMessagesDeclaration\` needs to point to a JSON file. Received: ${fullPath}`
        );
      }

      // Keep this as a runtime check and don't replace
      // this with a constant during the build process
      const env = process.env['NODE_ENV'.trim()];

      compileDeclaration(messagesPath);

      if (env === 'development') {
        startWatching(messagesPath);
      }
    }
  });
}

function startWatching(messagesPath: string) {
  const watcher = watchFile(messagesPath, () => {
    compileDeclaration(messagesPath, true);
  });

  process.on('exit', () => {
    watcher.close();
  });
}

function compileDeclaration(messagesPath: string, async: true): Promise<void>;
function compileDeclaration(messagesPath: string, async?: false): void;
function compileDeclaration(
  messagesPath: string,
  async = false
): void | Promise<void> {
  const declarationPath = messagesPath.replace(/\.json$/, '.d.json.ts');

  function createDeclaration(content: string) {
    return `// This file is auto-generated by next-intl, do not edit directly.
// See: https://next-intl.dev/docs/workflows/typescript#messages-arguments

declare const messages: ${content.trim()};
export default messages;`;
  }

  if (async) {
    return fs.promises
      .readFile(messagesPath, 'utf-8')
      .then((content) =>
        fs.promises.writeFile(declarationPath, createDeclaration(content))
      );
  }

  const content = fs.readFileSync(messagesPath, 'utf-8');
  fs.writeFileSync(declarationPath, createDeclaration(content));
}
